#include "bp/bp_unicode.h"
#include "bp/bp_error.h"

static const bp_char32_t _BP_CODEPOINT_MAX = 0x10FFFF;

_BP_INLINE static bp_bool _bp_u8_is_hibyte(bp_char8_t code)
{
	return _BP_BOOL_CAST((code & 0xC0) == 0x80);
}

_BP_INLINE static bp_char8_t _bp_u8_hibyte_decode(bp_char8_t code)
{
	return code & 0x3F;
}

_BP_INLINE static bp_bool _bp_u8_is_11bits_leadbyte(bp_char8_t code)
{
	return _BP_BOOL_CAST((code & 0xE0) == 0xC0);
}

_BP_INLINE static bp_char8_t _bp_u8_11bits_leadbyte_decode(bp_char8_t code)
{
	return code & 0x1F;
}

_BP_INLINE static bp_bool _bp_u8_is_16bits_leadbyte(bp_char8_t code)
{
	return _BP_BOOL_CAST((code & 0xF0) == 0xE0);
}

_BP_INLINE static bp_char8_t _bp_u8_16bits_leadbyte_decode(bp_char8_t code)
{
	return code & 0x0F;
}

_BP_INLINE static bp_bool _bp_u8_is_21bits_leadbyte(bp_char8_t code)
{
	return _BP_BOOL_CAST((code & 0xF8) == 0xF0);
}

_BP_INLINE static bp_char8_t _bp_u8_21bits_leadbyte_decode(bp_char8_t code)
{
	return code & 0x07;
}

static bp_char8_t _BP_UTF8_BOM[3] = {0xEF, 0xBB, 0xBF};
static const bp_char8_t * _bp_u8next(
	const bp_char8_t *u8, 
	size_t u8size,
	bp_char32_t *charater,
	size_t *next_u8char_count)
{
	bp_char8_t code = 0;
	size_t u8char_count = 0;
	if (!u8 || u8size == 0)
	{
		return 0;
	}

	while(u8size > 0)
	{
		code = *u8;
		if (code < 0x80)
		{
			u8char_count = 1;
		}
		else if (_bp_u8_is_11bits_leadbyte(code))
		{
			u8char_count = u8size >= 2 && _bp_u8_is_hibyte(u8[1]) ? 2 : 0;
		}
		else if (_bp_u8_is_16bits_leadbyte(code))
		{
			u8char_count = u8size >= 3 
				&& _bp_u8_is_hibyte(u8[1]) 
				&& _bp_u8_is_hibyte(u8[2]) ? 3 : 0;

			if (u8char_count == 3 
				&& memcmp(u8, _BP_UTF8_BOM, sizeof(_BP_UTF8_BOM)) == 0)
			{
				u8size -= u8char_count;
				u8 += u8char_count;
				continue;
			}

		}
		else if (_bp_u8_is_21bits_leadbyte(code))
		{
			u8char_count = u8size >= 4 
				&& _bp_u8_is_hibyte(u8[1]) 
				&& _bp_u8_is_hibyte(u8[2]) 
				&& _bp_u8_is_hibyte(u8[3]) ? 4 : 0;
		}
		else
		{
			u8char_count = 0;
		}

		if (u8char_count == 0)
		{
			return NULL;
		}

		_BP_SAFE_SET_PTR(next_u8char_count, u8char_count);
		if (charater)
		{
			*charater = 0;
			memcpy(charater, u8, u8char_count * sizeof(bp_char8_t));
		}

		return u8 + u8char_count;
	}

	return NULL;
}

_BP_EXPORT const bp_char8_t * _BP_API bp_u8next(
	const bp_char8_t *u8, 
	size_t u8size, 
	bp_char32_t *charater)
{
	return _bp_u8next(u8, u8size, charater, NULL);
}

_BP_EXPORT const bp_char8_t * _BP_API bp_u8next_codepoint(
	const bp_char8_t *u8, 
	size_t u8size, 
	bp_char32_t *codepoint)
{
	bp_char8_t u8char[4] = {0};
	size_t u8char_count = 0;
	bp_char32_t value = 0;
	const bp_char8_t *nextu8 = _bp_u8next(u8, u8size, (bp_char32_t *)u8char, &u8char_count);
	if (!nextu8)
	{
		return NULL;
	}

	if (codepoint)
	{
#define __BP_U8_OR_HIBYTE(i) do {\
	value |= (_bp_u8_hibyte_decode(u8char[i]) << ((u8char_count - i - 1) * 6));\
} while (0)

		if (u8char_count == 1)
		{
			value = u8char[0];
		}
		else if (u8char_count == 2)
		{
			value |= (_bp_u8_11bits_leadbyte_decode(u8char[0]) << 6);
			__BP_U8_OR_HIBYTE(1);
		}
		else if (u8char_count == 3)
		{
			value |= (_bp_u8_16bits_leadbyte_decode(u8char[0]) << 12);
			__BP_U8_OR_HIBYTE(1);
			__BP_U8_OR_HIBYTE(2);
		}
		else if (u8char_count == 4)
		{
			value |= (_bp_u8_21bits_leadbyte_decode(u8char[0]) << 18);
			__BP_U8_OR_HIBYTE(1);
			__BP_U8_OR_HIBYTE(2);
			__BP_U8_OR_HIBYTE(3);
		}

		*codepoint = value;
	}

	return nextu8;
}

_BP_EXPORT size_t _BP_API bp_u8encode(bp_char32_t codepoint, bp_bool input_is_little_endian, bp_char32_t *charater)
{
	bp_char8_t u8s[4] = {0};
	size_t size = 0;

	if (!input_is_little_endian)
	{
		codepoint = _BP_DWORD_BE_TO_LE(codepoint);
	}

	if (codepoint > _BP_CODEPOINT_MAX)
	{
		return 0;
	}

	//b0xxxxxxx -- 7bits
	if (codepoint < 0x80)
	{
		u8s[0] = _BP_MAKE_BYTE(codepoint);
		size = 1;
	}
	//utf32: b00000xxx, bxxxxxxxx -- 11bits
	//utf8:  b110xxxxx, b10xxxxxx
	else if (codepoint < 0x800)
	{
		u8s[0] = (_BP_MAKE_BYTE_MASK(codepoint >> 6, 0x1F) | 0xC0);
		u8s[1] = (_BP_MAKE_BYTE_MASK(codepoint, 0x3F) | 0x80);
		size = 2;
	}
	//utf32: b00000000, bxxxxxxxx, bxxxxxxxx - 16bits
	//utf8:  b1110xxxx, b10xxxxxx, b10xxxxxx
	else if (codepoint < 0x10000)
	{
		u8s[0] = (_BP_MAKE_BYTE_MASK(codepoint >> 12, 0x0F) | 0xE0);
		u8s[1] = (_BP_MAKE_BYTE_MASK(codepoint >> 6, 0x3F) | 0x80);
		u8s[2] = (_BP_MAKE_BYTE_MASK(codepoint, 0x3F) | 0x80);
		size = 3;
	}
	//utf32: b00000000, b000xxxxx, bxxxxxxxx, bxxxxxxxx -- 21bits
	//utf8:  b11110xxx, b10xxxxxx, b10xxxxxx, b10xxxxxx
	else if (codepoint < 0x200000)
	{
		u8s[0] = (_BP_MAKE_BYTE_MASK(codepoint >> 18, 0x07) | 0xF0);
		u8s[1] = (_BP_MAKE_BYTE_MASK(codepoint >> 12, 0x3F) | 0x80);
		u8s[2] = (_BP_MAKE_BYTE_MASK(codepoint >> 6, 0x3F) | 0x80);
		u8s[3] = (_BP_MAKE_BYTE_MASK(codepoint, 0x3F) | 0x80);
		size = 4;
	}
	else
	{
		return 0;
	}

	_BP_SAFE_SET_PTR(charater, *((bp_char32_t *)u8s));
	return size;
}

static const bp_char16_t * _bp_u16next(
	const bp_char16_t *u16, 
	size_t u16size, 
	bp_byte_order input_byteorder, 
	bp_char32_t *charater,
	size_t *next_charcount)
{
	size_t charcount = 0;
	bp_char16_t lword = 0;
	bp_char16_t hword = 0;

	if (!u16 || u16size == 0)
	{
		return NULL;
	}

	while(u16size > 0)
	{
		lword = input_byteorder == bp_big_endian ? _BP_WORD_BE_TO_LE(u16[0]) : u16[0];
		if (lword == 0xFEFF)
		{
			u16size -= 1;
			u16 += 1;
			continue;
		}

		if (u16size == 1)
		{
			charcount = 1;
		}
		else 
		{
			hword = input_byteorder == bp_big_endian ? _BP_WORD_BE_TO_LE(u16[1]) : u16[1];
			if (lword >= 0xD800 && lword <= 0xDBFF 
				&& hword >= 0xDC00 && hword <= 0xDFFF)
			{
				charcount = 2;
			}
			else
			{
				charcount = 1;
			}
		}

		_BP_ASSERT(charcount == 1 || charcount == 2);
		if (charater)
		{
			*((bp_char16_t *)charater) = lword;
			*((bp_char16_t *)charater + 1) = charcount == 2 ? hword : 0;
		}
		_BP_SAFE_SET_PTR(next_charcount, charcount);

		return u16 + charcount;
	}
	
	return NULL;
}

_BP_EXPORT const bp_char16_t * _BP_API bp_u16next(
	const bp_char16_t *u16, 
	size_t u16size, 
	bp_byte_order u16byteorder, 
	bp_char32_t *charater)
{
	return _bp_u16next(u16, u16size, u16byteorder, charater, NULL);
}

_BP_EXPORT const bp_char16_t * _BP_API bp_u16next_codepoint(
	const bp_char16_t *u16, 
	size_t u16size, 
	bp_byte_order u16byteorder,
	bp_char32_t *codepoint)
{
	bp_char16_t u16char[2] = {0};
	size_t next_charcount = 0;
	const bp_char16_t *next = _bp_u16next(u16, u16size, u16byteorder, (bp_char32_t *)u16char, &next_charcount);
	if (!next)
	{
		return NULL;
	}

	if (codepoint)
	{
		if(next_charcount == 2)
		{
			*codepoint = (((u16char[0] - 0xD7C0) << 10) & 0x1FFC00) | (u16char[1] & 0x3FF);
		}
		else
		{
			*codepoint = u16char[0];
		}
	}

	return next;
}

_BP_EXPORT size_t _BP_API bp_u16encode(bp_char32_t codepoint, bp_bool input_is_little_endian, bp_char32_t *charater)
{
	bp_char16_t encoded[2] = {0};
	size_t encode_count = 0;
	if (!input_is_little_endian)
	{
		codepoint = _BP_DWORD_BE_TO_LE(codepoint);
	}

	if (codepoint > _BP_CODEPOINT_MAX)
	{
		return 0;
	}

	if (codepoint <= 0xFFFF)
	{
		encode_count = 1;
		encoded[0] = codepoint;
	}
	else
	{
		encode_count = 2;
		encoded[0] = (0xD7C0 + ((codepoint >> 10) & 0x3FFFFF));
		encoded[1] = (0xDC00 | (codepoint & 0x3FF));
	}

	_BP_SAFE_SET_PTR(charater, *((bp_char32_t *)encoded));
	return encode_count;
}

//_BP_EXPORT size_t _BP_API bp_u16decode(const bp_char16_t *u16chars, size_t u16size, bp_char32_t *codepoint)
//{
//	bp_char32_t lword = 0, hword = 0;
//	bp_char32_t value = 0;
//	if (!u16chars || u16size == 0)
//	{
//		return 0;
//	}
//
//	lword = u16chars[0];
//	if (u16size == 1)
//	{
//		_BP_SAFE_SET_PTR(codepoint, lword);
//		return 1;
//	}
//
//	hword = u16chars[1];
//	if (lword >= 0xD800 && lword <= 0xDBFF 
//		&& hword >= 0xDC00 && hword <= 0xDFFF)
//	{
//		value |= (((lword - 0xD7C0) << 10) & 0x1FFC00);
//		value |= (hword & 0x3FF);
//		_BP_SAFE_SET_PTR(codepoint, value);
//		return 2;
//	}
//	else
//	{
//		_BP_SAFE_SET_PTR(codepoint, lword);
//		return 1;
//	}
//}

_BP_EXPORT size_t _BP_API bp_u8_to_u16(
	const bp_char8_t *u8, 
	size_t u8size, 
	bp_char16_t *u16, 
	size_t u16size)
{
	size_t u8offset = 0, u16encode = 0, u16offset = 0;
	const bp_char8_t *nextu8 = u8;
	bp_char32_t u16char = 0;
	bp_char32_t codepoint = 0;
	if (!u8 || u8size == 0)
	{
		return 0;
	}

	while(u8offset < u8size)
	{
		nextu8 = bp_u8next_codepoint(nextu8, u8size - u8offset, &codepoint);
		if (!nextu8)
		{
			return 0;
		}

		u16encode = bp_u16encode(codepoint, BP_TRUE, &u16char);
		if (u16encode == 0)
		{
			return 0;
		}

		if (u16 && u16size > 0)
		{
			if (u16offset + u16encode > u16size)
			{
				break;
			}

			memcpy(u16 + u16offset, &u16char, u16encode * sizeof(bp_char16_t));
		}

		u16offset += u16encode;
		u8offset = nextu8 - u8;
	}

	return u16offset;
}

_BP_EXPORT size_t _BP_API bp_u16_to_u8(
	const bp_char16_t *u16, 
	size_t u16size, 
	bp_byte_order u16byteorder, 
	bp_char8_t *u8, 
	size_t u8size)
{
	size_t u16offset = 0;
	size_t u8offset = 0;
	const bp_char16_t *u16next = u16;
	bp_char32_t codepoint = 0;
	bp_char32_t u8char = 0;
	size_t u8encode = 0;
	if (!u16 || u16size == 0)
	{
		return 0;
	}

	while(u16offset < u16size)
	{
		u16next = bp_u16next_codepoint(u16next, u16size - u16offset, u16byteorder, &codepoint);
		if (!u16next)
		{
			return 0;
		}


		u8encode = bp_u8encode(codepoint, BP_TRUE, &u8char);
		if (u8 && u8size > 0)
		{
			if (u8offset + u8encode > u8size)
			{
				break;
			}
			memcpy(u8 + u8offset, &u8char, u8encode * sizeof(bp_char8_t));
		}

		u8offset += u8encode;
		u16offset = u16next - u16;
	}

	return u8offset;
}
